/*
 * jxSwing Project (2013, 2014)
 * 
 * by Denilson Edinaldo Pinto
 * 
 * LGPL
 * Copyright (C) 2014
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package org.jxSwing.component.text;

import java.awt.Color;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Currency;
import java.util.Locale;

import javax.swing.SwingConstants;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.PlainDocument;

import org.jxUtils.Patterns;
import org.jxUtils.check.CheckUtil;

/**
 * @author Denilson Edinaldo Pinto
 * @project jxSwing
 * @package org.jxSwing.component.text
 * @date 06/04/2013 - 14:00:11
 */
public class NumberField extends TextField {
	
	private static final long serialVersionUID = -7472803703550242036L;
	
	private static final Color DEFAULT_FOREGROUND = new TextField().getForeground();
	
	private final DecimalFormat decimalFormat;
	
	private char decimalSeparator;
	
	private boolean acceptDecimal;
	
	private int decimalDigits;
	
	private boolean money = false;
	
	/**
	 * decimal is true
	 */
	public NumberField() {
	
		this(false);
	}
	
	/**
	 * @param acceptDecimal
	 */
	public NumberField(final boolean acceptDecimal) {
	
		this(acceptDecimal, false);
	}
	
	/**
	 * @param acceptDecimal
	 * @param money
	 */
	public NumberField(final boolean acceptDecimal, final boolean money) {
	
		this(acceptDecimal, Locale.getDefault(Locale.Category.FORMAT), money);
	}
	
	/**
	 * @param acceptDecimal
	 * @param decimalDigits
	 */
	public NumberField(final boolean acceptDecimal, final int decimalDigits) {
	
		this(acceptDecimal, decimalDigits, Locale.getDefault(Locale.Category.FORMAT));
	}
	
	/**
	 * @param acceptDecimal
	 * @param decimalDigits
	 * @param money
	 */
	public NumberField(final boolean acceptDecimal, final int decimalDigits, final boolean money) {
	
		this(acceptDecimal, decimalDigits, Locale.getDefault(Locale.Category.FORMAT), money);
	}
	
	/**
	 * @param acceptDecimal
	 * @param decimalDigits
	 * @param locale
	 */
	public NumberField(final boolean acceptDecimal, final int decimalDigits, final Locale locale) {
	
		this(acceptDecimal, decimalDigits, locale, false);
	}
	
	/**
	 * @param acceptDecimal
	 * @param decimalDigits
	 * @param locale
	 * @param money
	 */
	public NumberField(final boolean acceptDecimal, final int decimalDigits, final Locale locale, final boolean money) {
	
		this.acceptDecimal = acceptDecimal;
		this.decimalDigits = decimalDigits;
		decimalFormat = new DecimalFormat();
		
		decimalFormat.setDecimalFormatSymbols(new DecimalFormatSymbols(locale));
		
		decimalSeparator = decimalFormat.getDecimalFormatSymbols().getDecimalSeparator();
		this.money = money;
		
		initGUI();
	}
	
	/**
	 * @param acceptDecimal
	 * @param locale
	 */
	public NumberField(final boolean acceptDecimal, final Locale locale) {
	
		this(acceptDecimal, locale, false);
	}
	
	/**
	 * @param acceptDecimal
	 * @param locale
	 * @param money
	 */
	public NumberField(final boolean acceptDecimal, final Locale locale, final boolean money) {
	
		this.acceptDecimal = acceptDecimal;
		decimalFormat = new DecimalFormat();
		
		decimalFormat.setDecimalFormatSymbols(new DecimalFormatSymbols(locale));
		
		decimalDigits = money ? decimalFormat.getCurrency().getDefaultFractionDigits() : 0;
		decimalSeparator = decimalFormat.getDecimalFormatSymbols().getDecimalSeparator();
		this.money = money;
		
		initGUI();
	}
	
	/**
	 * @param acceptDecimal
	 * @param currency
	 */
	public NumberField(final Currency currency) {
	
		decimalFormat = new DecimalFormat();
		
		decimalFormat.setCurrency(currency);
		
		acceptDecimal = currency.getDefaultFractionDigits() > 0;
		decimalDigits = currency.getDefaultFractionDigits();
		decimalSeparator = decimalFormat.getDecimalFormatSymbols().getDecimalSeparator();
		money = true;
		
		initGUI();
	}
	
	/**
	 *
	 */
	private void initGUI() {
	
		setText(getSymbol() + "0");
		setHorizontalAlignment(SwingConstants.RIGHT);
		
		addCaretListener(new CaretListener() {
			
			@Override
			public void caretUpdate(final CaretEvent caretEvent) {
			
				setForeground(getText().contains("-") ? Color.RED : DEFAULT_FOREGROUND);
				
				// Se o Prefixo foi apagado, inclui novamente
				if(getText() != null && !getText().startsWith(getSymbol())) {
					
					new Thread(new Runnable() {
						
						@Override
						public void run() {
						
							try {
								Thread.sleep(100);
							}catch(final InterruptedException interruptedException) {
								// ignore
							}
							
							//
							if(getText() != null && !getText().trim().isEmpty() && getText().trim().length() >= getSymbol().length()) {
								
								String text = "";
								
								//
								for(int index = 0; index < getText().length(); index++) {
									
									char ch = '\0';
									
									try {
										ch = getText().charAt(index);
										
									}catch(NullPointerException | StringIndexOutOfBoundsException exception) {
										// ignore
									}
									
									//
									if(ch == '-' || Character.isDigit(ch)) {
										
										try {
											text = getText().substring(index).trim();
											
										}catch(NullPointerException | StringIndexOutOfBoundsException exception) {
											// ignore
										}
										
										break;
									}
								}
								
								//
								if( !text.trim().isEmpty() && Patterns.isNumber(text)) {
									
									setText(getSymbol() + text);
									
								}else {
									
									setText(getSymbol());
								}
								
							}else {
								
								setText(getSymbol());
							}
						}
					}).start();
				}
			}
		});
	}
	
	/**
	 *
	 */
	@Override
	protected Document createDefaultModel() {
	
		return new NumberDocument(this);
	}
	
	/**
	 * @return the BigDecimal value
	 */
	public final BigDecimal getBigDecimalValue() {
	
		return BigDecimal.valueOf(getDoubleValue());
	}
	
	/**
	 * @return the BigInteger value
	 */
	public final BigInteger getBigIntegerValue() {
	
		return BigInteger.valueOf(getLongValue());
	}
	
	/**
	 * @return the currency
	 */
	public final Currency getCurrency() {
	
		return getFormat().getCurrency();
	}
	
	/**
	 * @return the decimalDigits
	 */
	public final int getDecimalDigits() {
	
		return decimalDigits;
	}
	
	/**
	 * @return the decimalSeparator
	 */
	public final char getDecimalSeparator() {
	
		return decimalSeparator;
	}
	
	/**
	 * @return the double value
	 */
	public final Double getDoubleValue() {
	
		final String value = getValue().trim();
		
		final String valueAux = value.replace(".", "").replace("-", "").trim();
		
		return valueAux.isEmpty() ? 0.00 : Double.parseDouble(value);
	}
	
	/**
	 * @return the float value
	 */
	public final Float getFloatValue() {
	
		return getDoubleValue().floatValue();
	}
	
	/**
	 * @return the decimal format
	 */
	public DecimalFormat getFormat() {
	
		return decimalFormat;
	}
	
	/**
	 * @return the int value
	 */
	public final Integer getIntValue() {
	
		return getDoubleValue().intValue();
	}
	
	/**
	 * @return the long value
	 */
	public final Long getLongValue() {
	
		return getDoubleValue().longValue();
	}
	
	/**
	 * @return the short value
	 */
	public final Short getShortValue() {
	
		return getDoubleValue().shortValue();
	}
	
	/**
	 * @return the currency symbol
	 */
	public final String getSymbol() {
	
		return isMoney() ? getCurrency().getSymbol() : "";
	}
	
	/**
	 * @return the number in String
	 */
	public String getValue() {
	
		return getText().trim().replace(getSymbol(), "").replace(String.valueOf(decimalSeparator), ".");
	}
	
	/**
	 * @return the acceptDecimal
	 */
	public final boolean isAcceptDecimal() {
	
		return acceptDecimal;
	}
	
	/**
	 * @return is money
	 */
	public boolean isMoney() {
	
		return money;
	}
	
	/**
	 * @param acceptDecimal
	 *        the acceptDecimal to set
	 */
	public final void setAcceptDecimal(final boolean acceptDecimal) {
	
		this.acceptDecimal = acceptDecimal;
	}
	
	/**
	 * @param decimalDigits
	 *        the decimalDigits to set
	 */
	public final void setDecimalDigits(final int decimalDigits) {
	
		this.decimalDigits = decimalDigits < 0 ? 0 : decimalDigits;
		
		acceptDecimal = decimalDigits > 0;
	}
	
	/**
	 * @param decimalSeparator
	 *        the decimalSeparator to set
	 */
	public final void setDecimalSeparator(final char decimalSeparator) {
	
		this.decimalSeparator = decimalSeparator;
	}
	
	/**
	 * @param money
	 */
	public void setMoney(final boolean money) {
	
		this.money = money;
		
		setDecimalDigits(money ? decimalFormat.getCurrency().getDefaultFractionDigits() : 0);
		setAcceptDecimal(getDecimalDigits() > 0);
		setValue(getBigDecimalValue());
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.text.JTextComponent#setText(java.lang.String)
	 */
	@Override
	public void setText(final String text) {
	
		super.setText(text.replace(".", String.valueOf(decimalSeparator)));
		
		setForeground(getIntValue() < 0 ? Color.RED : DEFAULT_FOREGROUND);
	}
	
	/**
	 * @param value
	 */
	public void setValue(final BigDecimal value) {
	
		final BigDecimal roundValue = value.setScale(decimalDigits, BigDecimal.ROUND_HALF_UP);
		
		setText(getSymbol() + roundValue.toPlainString());
	}
	
	/**
	 * @param value
	 */
	public void setValue(final BigInteger value) {
	
		setText(getSymbol() + value);
	}
	
	/**
	 * @param value
	 */
	public void setValue(final Double value) {
	
		setValue(new BigDecimal(value));
	}
	
	/**
	 * @param value
	 */
	public void setValue(final Float value) {
	
		setValue(new BigDecimal(value));
	}
	
	/**
	 * @param value
	 */
	public void setValue(final Integer value) {
	
		setText(getSymbol() + value);
	}
	
	/**
	 * @param value
	 */
	public void setValue(final Long value) {
	
		setText(getSymbol() + value);
	}
	
	/**
	 * @param value
	 */
	public void setValue(final Short value) {
	
		setText(getSymbol() + value);
	}
	
	/**
	 * @param value
	 */
	public void subtract(final BigDecimal value) {
	
		setValue(getBigDecimalValue().subtract(value));
	}
	
	/**
	 * @param value
	 */
	public void subtract(final BigInteger value) {
	
		setValue(getBigIntegerValue().subtract(value));
	}
	
	/**
	 * @param value
	 */
	public void subtract(final Double value) {
	
		setValue(getDoubleValue() - value);
	}
	
	/**
	 * @param value
	 */
	public void subtract(final Float value) {
	
		setValue(getFloatValue() - value);
	}
	
	/**
	 * @param value
	 */
	public void subtract(final Integer value) {
	
		setValue(getIntValue() - value);
	}
	
	/**
	 * @param value
	 */
	public void subtract(final Long value) {
	
		setValue(getLongValue() - value);
	}
	
	/**
	 * @param value
	 */
	public void subtract(final Short value) {
	
		setValue(getShortValue() - value);
	}
	
	/**
	 * @param value
	 */
	public void sum(final BigDecimal value) {
	
		setValue(getBigDecimalValue().add(value));
	}
	
	/**
	 * @param value
	 */
	public void sum(final BigInteger value) {
	
		setValue(getBigIntegerValue().add(value));
	}
	
	/**
	 * @param value
	 */
	public void sum(final Double value) {
	
		setValue(getDoubleValue() + value);
	}
	
	/**
	 * @param value
	 */
	public void sum(final Float value) {
	
		setValue(getFloatValue() + value);
	}
	
	/**
	 * @param value
	 */
	public void sum(final Integer value) {
	
		setValue(getIntValue() + value);
	}
	
	/**
	 * @param value
	 */
	public void sum(final Long value) {
	
		setValue(getLongValue() + value);
	}
	
	/**
	 * @param value
	 */
	public void sum(final Short value) {
	
		setValue(getShortValue() + value);
	}
	
	/**
	 * @author Denilson Edinaldo Pinto
	 * @project jxSwing
	 * @package org.jxSwing.component.text
	 * @date 14/09/2012 - 20:57:28
	 */
	static class NumberDocument extends PlainDocument {
		
		/**
         *
         */
		private static final long serialVersionUID = 2423818832253285174L;
		
		private final NumberField txtNumber;
		
		/**
		 * @param txtNumber
		 */
		public NumberDocument(final NumberField txtNumber) {
		
			this.txtNumber = txtNumber;
		}
		
		/*
		 * (non-Javadoc)
		 * 
		 * @see javax.swing.text.PlainDocument#insertString(int, java.lang.String, javax.swing.text.AttributeSet)
		 */
		@Override
		public void insertString(int offs, final String str, final AttributeSet a) throws BadLocationException {
		
			//
			if(str == null || str.trim().isEmpty()) {
				
				return;
			}
			
			String strAux = str.trim();
			
			//
			if(CheckUtil.isAbsoluteEmpty(txtNumber.getText())) {
				
				//
				if(txtNumber.getSymbol().equals(strAux)) {
					
					super.insertString(offs, txtNumber.getSymbol(), a);
					
					return;
					
				}else {
					
					strAux = strAux.replace(txtNumber.getSymbol(), "");
					
					//
					if(Patterns.isInteger(strAux)) {
						
						String dec = "";
						
						//
						if(txtNumber.getDecimalDigits() > 0) {
							
							strAux += txtNumber.getDecimalSeparator();
							
							//
							while(dec.length() < txtNumber.getDecimalDigits()) {
								
								dec = dec.concat("0");
							}
						}
						
						super.insertString(offs, txtNumber.getSymbol().concat(strAux).concat(dec), a);
						
						return;
						
					}else if(Patterns.isDouble(strAux)) {
						
						String[] intDec = strAux.split(".");
						
						//
						if(intDec.length == 0) {
							
							intDec = strAux.split(",");
						}
						
						//
						if(intDec.length == 2 && intDec[1].length() < txtNumber.getDecimalDigits()) {
							
							//
							while(intDec[1].length() != txtNumber.getDecimalDigits()) {
								
								intDec[1] += "0";
								strAux = strAux.concat("0");
							}
						}
						
						super.insertString(offs, txtNumber.getSymbol().concat(strAux), a);
						
						return;
					}
				}
				
			}else if(Patterns.isNumber(txtNumber.getText().trim()) && txtNumber.getIntValue() == 0 && Patterns.isNumber(strAux)) {
				
				super.insertString(offs, strAux, a);
				
				return;
			}
			
			int carretIndex = txtNumber.getCaretPosition();
			final int negativeIndex = txtNumber.getText().indexOf("-");
			final int decimalSeparatorIndex = txtNumber.getText().indexOf(txtNumber.getDecimalSeparator());
			final int prefixIndex = txtNumber.getText().indexOf(txtNumber.getSymbol());
			final int prefixLength = txtNumber.getSymbol().length();
			
			//
			if(carretIndex < prefixIndex + prefixLength) {
				
				txtNumber.setCaretPosition(prefixIndex + prefixLength);
				carretIndex = txtNumber.getCaretPosition();
				offs = carretIndex;
			}
			
			char ch = '\0';
			
			// Para cada caracter
			for(int index = 0; index < strAux.length(); index++) {
				
				ch = strAux.charAt(index);
				
				// Se não for um número
				if( !Character.isDigit(ch)) {
					
					// Se aceita decimal e o caracter é um decimal
					if(txtNumber.isAcceptDecimal() && ch == txtNumber.getDecimalSeparator() && carretIndex >= prefixIndex + prefixLength && carretIndex > negativeIndex) {
						
						// Se já existe um separador de decimal, não insere novamente
						if(txtNumber.getText().contains(String.valueOf(txtNumber.getDecimalSeparator()))) {
							
							return;
						}
						
					}else if(ch == '-' && carretIndex == prefixIndex + prefixLength && !txtNumber.getText().contains("-")) {
						
						break;
						
					}else {
						
						return;
					}
					
				}else {
					
					//
					if(carretIndex <= prefixIndex || carretIndex <= prefixIndex + prefixLength - 1) {
						
						return;
						
					}else if(carretIndex > decimalSeparatorIndex) {
						
						//
						final String[] values = super.getText(0, super.getLength()).split(String.valueOf(txtNumber.getDecimalSeparator()));
						
						//
						if(values.length == 2 && values[1].length() + 1 > txtNumber.getDecimalDigits()) {
							
							return;
						}
					}
				}
			}
			
			// adiciona a String informada
			super.insertString(offs, strAux, a);
		}
	}
}
